Skip to content

Feature/custom solver support - #68

Draft
adefabian wants to merge 3 commits into
mainfrom
feature/custom_solver_support
Draft

Feature/custom solver support#68
adefabian wants to merge 3 commits into
mainfrom
feature/custom_solver_support

Conversation

@adefabian

Copy link
Copy Markdown
Collaborator

Summary

Customers can now ship their own QuerySolver (say, one that reshapes their raw
tables into the Impulse silver schema) without touching Impulse core.

Solvers register themselves by name, the report config picks one by that name through the
existing query_engine.solver field, and each solver can bring its own
SolverConfig subclass whose extra fields get checked at parse time.

Config picks a solver by registered name, not by class path. So a config can't
make Impulse import and run some arbitrary class. What solvers exist depends
entirely on what the driver imports.

DefaultSolver moves onto this mechanism with no behavior change. It registers
under "DefaultSolver" (plus the old "DeltaSolver" / "KeyValueStoreSolver"
aliases), so existing configs keep working.

Changes

  • Registry (solvers/registry.py): @register_solver(name, config_cls, *, aliases, overwrite) plus resolve_registration / is_registered / registered_names. Maps a name to (solver_cls, config_cls), rejects conflicting duplicates, and lists the known names when a lookup misses.
  • Uniform construction (QuerySolver.from_config + solvers/solver_context.py): a frozen SolverBuildContext (spark, solver_config, is_raw_data, drop_implausible_data, raw_encoder) is the one place construction inputs live. Base from_config passes just solver_config; DefaultSolver overrides it to also wire spark and the raw-data flags. The report factory builds any solver the same way, whatever its constructor looks like.
  • Source-loading seams on QuerySolver: load_container_tags, load_container_metrics, load_channel_tags, load_channel_metrics, load_channel_mapping, load_unit_conversion, load_channels. Defaults read the single configured table (same as before); a custom solver overrides them to combine or reshape raw sources. DefaultSolver's reads now go through these seams.
  • Config (config_parser.py): query_engine.solver is a plain str now (any registered name). Solvers becomes a StrEnum so old == Solvers.DEFAULT_SOLVER comparisons still hold. A mode="before" validator re-validates solver_config through the selected solver's config_cls, so a custom solver's fields are enforced at parse time and an unregistered name fails early as a ValidationError.
  • Report factory (core/report.py): create_solver resolves the name through the registry and calls from_config(ctx) instead of a hardcoded match. Incremental change detection reads containers via solver.load_container_metrics(...), so a reshaping solver feeds the same container set into upsert detection.
  • Docs (docs/impulse/docs/data_model/ingestion.md): a "Registering a custom solver" section covering subclassing DefaultSolver, declaring a SolverConfig subclass, selecting it by name, and importing the package so registration runs.

Test Plan

  • Unit tests added/updated
  • Manual testing completed
  • Documentation updated (if applicable)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • No new linter warnings introduced

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.95918% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.93%. Comparing base (f654d22) to head (1a4a741).

Files with missing lines Patch % Lines
src/impulse_reporting/config/config_parser.py 88.23% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #68      +/-   ##
==========================================
+ Coverage   88.72%   88.93%   +0.20%     
==========================================
  Files          59       61       +2     
  Lines        4826     4906      +80     
  Branches      552      556       +4     
==========================================
+ Hits         4282     4363      +81     
+ Misses        445      444       -1     
  Partials       99       99              
Files with missing lines Coverage Δ
...ery_engine/analyze/query/solvers/default_solver.py 97.68% <100.00%> (+0.03%) ⬆️
...query_engine/analyze/query/solvers/query_solver.py 90.36% <100.00%> (+2.48%) ⬆️
...lse_query_engine/analyze/query/solvers/registry.py 100.00% <100.00%> (ø)
...ery_engine/analyze/query/solvers/solver_context.py 100.00% <100.00%> (ø)
src/impulse_reporting/core/report.py 92.49% <100.00%> (+0.99%) ⬆️
src/impulse_reporting/config/config_parser.py 96.27% <88.23%> (-0.99%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant